package sim.coalitional;

import sim.helpers.Helper;
import java.util.LinkedList;

public class Coalition {

    private int id;
    private double value;
    private LinkedList<Node> nodeList;
    private Head head;
    Helper helper;

    public Coalition(int id, Helper helper) {
        this.id = id;
        this.value = 0;
        this.helper = helper;
        this.nodeList = new LinkedList<Node>();
    }

    public boolean addNode(Node n) {
        if (!nodeList.contains(n)) {
            n.setCoalitionId(this.id);
            getNodeList().add(n);
            updateNodeValues();
            this.value = getValue();
            return true;
        }
        return false;
    }

    private void calculateValue() {
        double tmpValue = 0;
        for (Node node : getNodeList()) {
            tmpValue += node.getValue();
        }
        this.value = tmpValue;
        return;
    }

    private void updateNodeValues() {
        for (Node node : getNodeList()) {
            node.setValue((getNodeList().size() - 1) * getAccuracy(node));
        }
        this.calculateValue();
    }

    private double getAccuracy(Node node) {
        double tmp = helper.pdf(helper.getMaximumIntraCoalitionDistance(), helper.SD);
        double normalizator = 0;
        if (tmp != 1) {
            normalizator = 1 / (1 - (tmp));
        }
        double accuracy = (1 - (helper.pdf(helper.getMaximumIntraCoalitionDistance(), helper.SD) / helper.pdf(getDistanceMetric(node), helper.SD))) * normalizator;
        return accuracy;
    }

    private double getDistanceMetric(Node n) {
        return getMaxDistance(n);
    }

    public void printMembers() {
        if (this.nodeList.size() == 0) {
            System.out.println("Coalition " + id + " has no members.");
        } else {
            System.out.print("Coalition " + this.id + " has the following " + this.getNodeList().size() + " members:");
            for (Node node : getNodeList()) {
                System.out.print(" " + node.getId());
            }
            System.out.print("\n");
        }
    }

    public boolean checkMergingWith(Coalition candidate) {
        boolean ret = false;
        if (candidate.head.getId() != this.head.getId()) {
            return ret;
        }
        Coalition mergedCoalition = new Coalition(-1, this.helper);
        mergedCoalition.setHead(this.head);
        for (Node node : getNodeList()) {
            mergedCoalition.addNode(node.clone());
        }
        for (Node node : candidate.getNodeList()) {
            mergedCoalition.addNode(node.clone());
        }
        if (isStandardOptimal(mergedCoalition, candidate)) {
            ret = true;
        }
        for (Node node : mergedCoalition.getNodeList()) {
            node = null;
        }
        mergedCoalition = null;
        return ret;
    }

    public void mergeWith(Coalition candidate) {
        for (Node node : candidate.nodeList) {
            addNode(node);
        }
        candidate.nodeList.clear();
    }

    private boolean isParetoOptimal(Coalition mergedCoalition, Coalition candidate) {
        boolean ret = true;
        for (Node mNode : mergedCoalition.getNodeList()) {
            for (Node cNode : candidate.getNodeList()) {
                for (Node tNode : this.nodeList) {
                    if (mNode.getId() == cNode.getId()) {
                        if (mNode.getValue() <= cNode.getValue()) {
                            return false;
                        }
                    } else if (mNode.getId() == tNode.getId()) {
                        if (mNode.getValue() <= tNode.getValue()) {
                            return false;
                        }
                    }
                }
            }
        }
        return ret;
    }

    private boolean isStandardOptimal(Coalition mergedCoalition, Coalition candidate) {
        return (mergedCoalition.getValue() > (this.getValue() + candidate.getValue()));
    }

    private double getMaxDistance(Node n) {
        double maxDistance = 0;
        for (Node node : getNodeList()) {
            double currentDistance = helper.distance(n, node);
            if (currentDistance > maxDistance) {
                maxDistance = currentDistance;
            }
        }
        return maxDistance;
    }

    private double getAvgDistance(Node n) {
        double sumOfDistances = 0;
        for (Node node : getNodeList()) {
            sumOfDistances += helper.distance(n, node);
        }
        return sumOfDistances / getNodeList().size();
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public double getValue() {
        calculateValue();
        return value;
    }

    public Head getHead() {
        return head;
    }

    public void setHead(Head head) {
        this.head = head;
    }

    public LinkedList<Node> getNodeList() {
        return nodeList;
    }

    public void setNodeList(LinkedList<Node> nodeList) {
        this.nodeList = nodeList;
    }

    public double getAvgDistanceMetric() {
        double ret = 0;
        for (Node node : nodeList) {
            ret += getDistanceMetric(node);
        }
        return ret / nodeList.size();
    }
    /***
     * TESTING PURPOSES
     */
//    public boolean addToMergeList(Coalition candidate) {
//        boolean ret = true;
//        if (!this.coalitionMergeList.contains(candidate)) {
//            this.coalitionMergeList.add(candidate);
//            Collections.sort(this.coalitionMergeList, new CoalitionValueComparer(this));
//        }
//        return ret;
//    }
//
//    public String getMergeListString() {
//        String ret = "";
//        for (Coalition c : this.getCoalitionMergeList()) {
//            ret += (" " + c.getId());
//        }
//        return ret;
//    }
//    public double getMaximumDistanceFromCoalition(Coalition candidate) {
//        double ret = 0;
//        for (Node nodet : this.getNodeList()) {
//            for (Node nodeC : candidate.getNodeList()) {
//                if (helper.distance(nodet, nodeC) > ret) {
//                    ret = helper.distance(nodet, nodeC);
//                }
//            }
//        }
//        return ret;
//    }
//
//    public double getPossibleValueAfterMergingWith(Coalition candidate) {
//        double ret = 0;
//        if (!checkMergingWith(candidate)) {
//            ret = -1000; //Return negative. -1000 to be sure ;)
//        } else {
//            Coalition mergedCoalition = new Coalition(-1, this.helper);
//            mergedCoalition.setHead(this.head);
//            for (Node node : getNodeList()) {
//                mergedCoalition.addNode(node.clone());
//            }
//            for (Node node : candidate.getNodeList()) {
//                mergedCoalition.addNode(node.clone());
//            }
//            ret = mergedCoalition.getValue();
//            mergedCoalition = null;
//        }
//        return ret;
//    }
}
